package com.skyline.energy.executor.impl;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.skyline.energy.cache.Cache;
import com.skyline.energy.cache.CacheManager;
import com.skyline.energy.cache.CacheObject;
import com.skyline.energy.cache.CacheResult;
import com.skyline.energy.definition.CacheDefinition;
import com.skyline.energy.definition.CacheDefinitionCollection;
import com.skyline.energy.definition.CacheDeleteDefinition;
import com.skyline.energy.definition.VersionUpdateDefinition;
import com.skyline.energy.exception.CacheUnreachableException;
import com.skyline.energy.exception.DaoGenerateException;
import com.skyline.energy.executor.DataAccessExecutor;
import com.skyline.energy.utils.CommonUtils;
import com.skyline.energy.utils.Page;

public class CacheExecutor implements DataAccessExecutor {
	private static final Log LOGGER = LogFactory.getLog(CacheExecutor.class);
	private CacheDefinitionCollection cacheDefinitionCollection;
	private final CacheManager cacheManager;
	private DataAccessExecutor dataAccessExecutor;

	public CacheExecutor(CacheManager cacheManager, Method method) throws DaoGenerateException {
		this.cacheManager = cacheManager;
		this.cacheDefinitionCollection = new CacheDefinitionCollection(method);
	}

	public void setDataAccessExecutor(DataAccessExecutor dataAccessExecutor) {
		this.dataAccessExecutor = dataAccessExecutor;
	}

	@Override
	public Object execute(Object obj, Object[] args) {
		if (!cacheDefinitionCollection.needCacheOpration()) {
			return dataAccessExecutor.execute(obj, args);
		}

		try {
			CacheResult cacheResult = beforeDataAccess(args);

			Object retVal = dataAccessExecutor.execute(obj, args);
			cacheResult.setRetVal(retVal);
			
			afterDataAccess(cacheResult, args);
			
			return retVal;
		} catch (CacheUnreachableException e) {
			return dataAccessExecutor.execute(obj, args);
		}
		
	}

	private CacheResult beforeDataAccess(Object[] args) throws CacheUnreachableException {
		CacheResult cacheResult = new CacheResult(false);

		CacheDefinition cacheDefinition = cacheDefinitionCollection.getCacheDefinition();

		if (cacheDefinition == null) {
			return cacheResult;
		}

		String key = cacheDefinition.generateCacheKey(args);
		if (StringUtils.isEmpty(key)) {
			LOGGER.debug("创建缓存KEY失败,不进行缓存！");
			return cacheResult;
		}
		cacheResult.setKey(key);

		Cache cache = cacheManager.getCache(cacheDefinition.getPool());
		cacheResult.setCache(cache);

		CacheObject oldItem = (CacheObject) cache.get(key);
		LOGGER.debug("从" + key + "获取缓存对象：" + oldItem);
		if (oldItem != null) {
			return compareVersionKey(cacheDefinition, cacheResult, oldItem, args);
		}

		return cacheResult;
	}

	private CacheResult compareVersionKey(CacheDefinition cacheDefinition, CacheResult cacheResult,
			CacheObject oldItem, Object[] args) {
		long itemVersion = oldItem.getVersion();
		Object cachedItem = oldItem.getCacheObject();

		Object[] params;
		boolean isReturnCollection = cacheDefinition.isReturnCollection();
		if (isReturnCollection) {
			params = args;
		} else {// 单个对象
			// 缓存对象为空或者不需要判断versionKey就作为命中处理
			if (cachedItem == null || StringUtils.isEmpty(cacheDefinition.getVkey())) {
				return processCacheHit(cacheResult, cachedItem, args);
			}

			params = new Object[] { cachedItem };
		}

		String versionKey = cacheDefinition.generateVersionKey(params);
		if (StringUtils.isEmpty(versionKey)) {
			LOGGER.debug("无法生成更新版本信息的缓存KEY，获取缓存失败");
			return cacheResult;
		}
		LOGGER.debug("生成版本信息的缓存KEY：" + versionKey);
		cacheResult.setVersionKey(versionKey);

		Cache cache = cacheResult.getCache();

		long currentVersion = getCurrentVersion(cache, versionKey);
		cacheResult.setCurrentVersion(currentVersion);

		if (currentVersion != 0 && currentVersion == itemVersion) { // 版本一致则表示有效
			return processCacheHit(cacheResult, cachedItem, args);
		} else {
			LOGGER.debug("当前版本为:" + currentVersion + "，缓存对象版本号为：" + itemVersion + "，Cache没有命中，直接调用DAO方法");
			return cacheResult;
		}
	}

	private long getCurrentVersion(Cache cache, String versionKey) {
		Long cachedVersion = (Long) cache.get(versionKey);
		LOGGER.debug("从" + versionKey + "获取版本缓存对象：" + cachedVersion);
		long currentVersion = 0;
		if (cachedVersion != null) {
			currentVersion = cachedVersion;
		}
		return currentVersion;
	}

	private CacheResult processCacheHit(CacheResult cacheResult, Object cachedItem, Object[] args) {
		LOGGER.debug("版本号一致，缓存命中");

		String key = cacheResult.getKey();
		cacheResult.setRetVal(cachedItem);

		CacheDefinition cacheDefinition = cacheDefinitionCollection.getCacheDefinition();
		Cache cache = cacheResult.getCache();

		if (cacheDefinition.isReturnCollection()) {
			Page page = CommonUtils.getPageArgument(args, cacheDefinition.getPageIndex());
			if (page != null) { // 判断分页参数是非被复用
				String pageKey = cacheDefinition.generatePageKey(args, key);
				LOGGER.debug("缓存为需要分页的缓存，页码(" + page.getCurpage() + ")，对应的分页缓存KEY为：" + pageKey);
				Page cachePage = (Page) cache.get(pageKey);
				page.setTotal(cachePage.getTotal());
				cacheResult.setPageKey(pageKey);
			}
		}
		cacheResult.setHit(true);
		return cacheResult;
	}

	private void afterDataAccess(CacheResult cacheResult, Object[] args) throws CacheUnreachableException {
		CacheDefinition cacheDefinition = cacheDefinitionCollection.getCacheDefinition();
		if (cacheDefinition == null) {
			updateVersion(cacheResult, args);
			deleteCache(cacheResult, args);
			return;
		}

		String key = cacheResult.getKey();
		Object exeResult = cacheResult.getRetVal();
		Cache cache = cacheResult.getCache();

		cacheResult = recacheVersionKey(cacheResult, args, cacheDefinition);

		long expire = cacheDefinition.getExpire();

		long currentVersion = cacheResult.getCurrentVersion();

		CacheObject newItem = new CacheObject((Serializable) exeResult, currentVersion);
		cache.add(key, newItem, expire);
		LOGGER.debug("缓存对象到：" + key + ",缓存时间：" + expire + "毫秒");

		if (cacheDefinition.isReturnCollection()) {
			int pageIndex = cacheDefinition.getPageIndex();
			Page page = CommonUtils.getPageArgument(args, pageIndex);
			if (page != null) {
				String pageKey = cacheResult.getPageKey();
				if (pageKey == null) {
					pageKey = cacheDefinition.generatePageKey(args, key);
				}
				if (!StringUtils.isEmpty(pageKey)) {
					cache.add(pageKey, page, expire);
					LOGGER.debug("缓存为需要分页的缓存，页码(" + page.getCurpage() + ")，缓存分页对象到：" + pageKey + ", 缓存时间：" + expire + "毫秒");
				}
			}
		}

	}

	private void updateVersion(CacheResult cacheResult, Object[] args) throws CacheUnreachableException {
		List<VersionUpdateDefinition> updates = cacheDefinitionCollection.getVersionUpdateDefinitions();
		if (updates == null) {
			return;
		}

		for (VersionUpdateDefinition definition : updates) {
			String versionKey = definition.generateVersionKey(args);
			if (versionKey == null) {
				LOGGER.info("无法生成更新版本信息的缓存KEY，更新缓存版本失败");
			}
			long currentVersion = System.currentTimeMillis(); // 改用系统时间作为版本号，防止重构后的版本号与对象的版本正好一致，而是对象无法失效
			if (versionKey != null) {
				Cache cache = cacheManager.getCache(definition.getPool());
				long expire = definition.getExpire();
				cache.add(versionKey, currentVersion, expire);
				LOGGER.debug("更新版本信息的缓存KEY：" + versionKey + "到版本" + currentVersion + ", 缓存时间：" + expire + "毫秒");
			}
		}
	}

	private void deleteCache(CacheResult cacheResult, Object[] args) throws CacheUnreachableException {
		List<CacheDeleteDefinition> deletes = cacheDefinitionCollection.getCacheDeleteDefinitions();
		if (deletes == null) {
			return;
		}

		for (CacheDeleteDefinition definition : deletes) {
			String key = definition.generateCacheKey(args);
			Cache cache = cacheManager.getCache(definition.getPool());
			cache.delete(key);
			LOGGER.debug("删除缓存KEY：" + key);
		}
	}

	private CacheResult recacheVersionKey(CacheResult cacheResult, Object[] args, CacheDefinition cacheDefinition) {
		long currentVersion = cacheResult.getCurrentVersion();
		String versionKey = cacheResult.getVersionKey();
		boolean isReturnCollection = cacheDefinition.isReturnCollection();
		Cache cache = cacheResult.getCache();
		Object result = cacheResult.getRetVal();

		if (currentVersion == 0) { // 原来的version已经因为时间等原因过期，需要重新缓存
			Long cachedCurrentVersion = null;
			// 第一次查询缓存，需要生产版本的key;
			if (versionKey == null) {
				if (isReturnCollection) {
					versionKey = cacheDefinition.generateVersionKey(args);
				} else if (result != null && StringUtils.isNotEmpty(cacheDefinition.getVkey())) {
					versionKey = cacheDefinition.generateVersionKey(new Object[] { result });
				} else {
					LOGGER.info("不需要缓存版本信息");
					return cacheResult;
				}

				if (versionKey == null) {
					LOGGER.info("无法生成更新版本信息的缓存KEY，缓存失败");
					return cacheResult;
				}
				LOGGER.debug("第一次查询缓存，需要生成版本信息的缓存KEY：" + versionKey);
				cachedCurrentVersion = (Long) cache.get(versionKey); // 由于第一次查询无法得知版本信息，所以在知道版本key后才需要再查一次版本信息

			}

			// 为空或者无意义才表明版本信息也需要缓存，否则版本信息就为缓存的版本信息
			if (versionKey != null && (cachedCurrentVersion == null || cachedCurrentVersion <= 0)) {
				currentVersion = System.currentTimeMillis(); // 改用系统时间作为版本号，防止重构后的版本号与对象的版本正好一致，而是对象无法失效
				long expire = cacheDefinition.getExpire();
				cache.add(versionKey, currentVersion, expire);
				LOGGER.debug("更新版本信息的缓存KEY：" + versionKey + "到版本" + currentVersion + ", 缓存时间：" + expire + "毫秒");
				cacheResult.setCurrentVersion(currentVersion);
			} else {
				if (cachedCurrentVersion == null || cachedCurrentVersion <= 0) {
					cachedCurrentVersion = System.currentTimeMillis(); // 改用系统时间作为版本号，防止重构后的版本号与对象的版本正好一致，而是对象无法失效
				}
				cacheResult.setCurrentVersion(cachedCurrentVersion);
			}
		}

		return cacheResult;
	}
}
